iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 7
1
Modern Web

RRR撞到不負責之 Laravel + Nuxt.js 踩坑全紀錄系列 第 7

Day 07. Controller 減重計畫 (Service 篇)

  • 分享至 

  • xImage
  •  

昨天有提到 service 會統合各項資源與應用,事實上並沒有明確的文章規範那些處理應該寫在 service 或是 controller,至少兩者之間所處理的事項小弟個人認為不如 repository 明確,而自己的習慣是將「不可分割的商務邏輯片段」視為一個單純的服務,會在 service 中獨立成一個 function。今天老樣子以 Post 為例。

  1. 同樣先建立 interface
namespace App\Services;

use Illuminate\Database\Eloquent\Model;

interface IService
{
    // ...
    public function update(Model $modelInstance, array $data);
    // ...
}
  1. 接下來將共用邏輯的基本服務實作在 abstract class 裡面。其中,因為範例需求,有調整 repository
<?php 

namespace App\Services;

use App\Repositories\IRepository;
use Illuminate\Database\Eloquent\Model;

abstract class BaseService implements IService
{
    protected $repository;
    protected $resourceClass;

    public function __construct(IRepository $repository, $resourceClass)
    {
        // 設定主要進行 CRUD 的 repository
        $this->repository = $repository;
        $this->resourceClass = $resourceClass;
    }

    // ...

    public function update(Model $modelInstance, array $data)
    {
        // 我們將 IRepository 的 update function 改為可以接收
        // Model 實例或是流水號 "id"
        return $this->repository->update($modelInstance, $data);
    }

    public function modelToAPIResource($model)
    {
        return new $this->resourceClass($model);
    }
    // ...
}
  1. 最後依需求實作 PostService,結果如下。
    首先更新文章情境為: 「系統收到文章更新需求與更新內容之後,需要先檢查更新者是管理者或作者本人,具有權限才得以更新文章,沒有權限就必須發送警告信給作者」。

    情境描述中,更新一篇文章「檢查更新者權限」、「更新文章」和「發送警告信」,三個動作是不論何種情境都必須綁在一起,所以會建立包含三者的一個服務 (如 updatePost)。

    至於更新者 ID 取得方式、更新失敗後的其他處理,皆有可能會依照不同情境有所不同,因此上述項目就不會被包在 updatePost 中。

    另外,我們也遵守將資料庫存取的邏輯撰寫於 repository,service 只進行整合與使用。同時,假設有各種不同更新 post 的情況,且未來權限檢查邏輯有所調整,我們只要修改 service 即可,呼叫 updatePost 的 controllers 就不需要逐一修改。

namespace App\Services;

use App\Http\Resources\Post;
use App\Repositories\PostRepository;
use App\Repositories\UserRepository;
use App\Utils\Messenger\EmailMessenger;

class PostService extends BaseService
{
    private $emailMessenger;
    private $userRepository;

    // DI 注入各種會用到的 repositories 和其他類別實例
    public function __construct(PostRepository $postRepository, UserRepository $userRepository, EmailMessenger $emailMessenger)
    {
        // 注入 BaseService 初始主要的 repository 和 resource
        parent::__construct($postRepository, Post::class);
        $this->userRepository = $userRepository;
        $this->emailMessenger = $emailMessenger;
    }

    // 注意 PHP 沒有像 Java, C#, C++ 中的 overloading 寫法,所以方法名稱不能相同
    public function updatePost($updatePersonId, $postId, array $data)
    {
        $updatePerson = $this->userRepository->readById($updatePersonId);
        $post = $this->repository->readById($postId);
        $isManager = $updatePerson && $updatePerson->isManager();
        $isAuthor = $updatePerson && $post && $post->user_id === $updatePerson->id;
        // 確認更新者權限
        if ($isManager || $isAuthor) {
            // 更新文章
            $updatedPost = $this->update($data);
            return $updatedPost;
        } else {
            // 寄出警告信
            $this->emailMessenger
                ->setTargrt($post->user->email)
                ->setTitle("Post update warrning")
                ->setMessage("Someone({$updatePerson->id}) try to update your post '{$post->title}' without permission")
                ->send();
            
            throw new \Exception("post update permission denied");
        }
    }
}

今天 service 在技術上相對簡單 (個人覺得至少比 DI 的理解簡單 XD),service 的困難雖不在技術而是在架構與設計。像小弟個人也覺得還有很多進步空間,但就是盡量符合 service 的概念、根據現有需求將各功能的耦合降低,讓日後維護或是撰寫測試 (其實測試應該是要最早寫才對)!

兩篇 controller 瘦身計畫之後,明天我們來看看瘦身後的 controller 吧!


上一篇
Day 06. Controller 減重計畫 (Repository 篇)
下一篇
Day 08. 瘦,是一種生活 - 減脂後的 Controller
系列文
RRR撞到不負責之 Laravel + Nuxt.js 踩坑全紀錄31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言